package com.socio.resources;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;

import net.sf.jsr107cache.Cache;
import net.sf.jsr107cache.CacheException;
import net.sf.jsr107cache.CacheManager;

import org.apache.commons.codec.binary.Base64;

import com.google.appengine.repackaged.org.json.JSONException;
import com.google.appengine.repackaged.org.json.JSONObject;
import com.socio.behaviors.DAOBehavior;
import com.socio.behaviors.UserFacebookIDBehavior;
import com.socio.behaviors.UserUDIDBehavior;
import com.socio.model.User;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.core.util.MultivaluedMapImpl;

@Path("/oauth")
public class OAuthResource {
	
	private final String appID = "135216369868379";
	private final String appSecret = "6f7052c7dde7ef72d1d0eec933cc7d10";
	private final String appAccessToken = "135216369868379|_NfZFkG90LjNQqVDswGlHhWUqo4";
	private final String redirectUri = "http://socioapp1.appspot.com/resources/oauth/redirect?udid=";
	
	@Path("/redirect")
    @GET
    @Produces(MediaType.TEXT_HTML)
    public String handleRedirect(@QueryParam("code") @DefaultValue("") final String code,
    		@QueryParam("error_reason") @DefaultValue("") String errorReason,
    		@QueryParam("udid") @DefaultValue("") final String udid) {
		
		// preliminary checks before any real work gets done
		if(!errorReason.isEmpty()) {
			return "<html><body>Socio will not be able to give you points for your Facebook updates until" +
					" you authorize us.</body></html>";
		}
		
		if(udid.isEmpty() || code.isEmpty()) {
			return "<html><body>Unauthorized access</body></html>";
		}
		
		// get access token for user
		Client client = Client.create();
		MultivaluedMap<String,String> queryParams = new MultivaluedMapImpl();
		queryParams.add("client_id", appID);
		queryParams.add("redirect_uri", redirectUri+ udid);
		queryParams.add("client_secret", appSecret);
		queryParams.add("code", code);
		ClientResponse response = client.resource("https://graph.facebook.com/oauth/access_token").queryParams(queryParams).accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
		String responseString = response.getEntity(String.class);
	    String accessToken = getParamsMap(responseString).get("access_token");
		if(accessToken == null)
			return "<html><body>Problem accepting authorization. Please try authorizing us again later.</body></html>";
		
		// get user's facebook id using access token
		queryParams.clear();
		queryParams.add("access_token", accessToken);
		response = client.resource("https://graph.facebook.com/me").queryParams(queryParams).get(ClientResponse.class);
		String json = response.getEntity(String.class);
		String facebookID = "";
		try {
			JSONObject jsonObject = new JSONObject(json);
			facebookID = jsonObject.getString("id");
		} catch (JSONException e) {
			return "<html><body>Problem accepting authorization. Please try authorizing us again later</body></html>";
		}
		
		// setup behaviors
        DAOBehavior userUDIDDAO = new UserUDIDBehavior();
        DAOBehavior userFacebookIDDAO = new UserFacebookIDBehavior();

        // get user with udid 
        // make sure facebook id is not overwritten with another one (person using someone else's phone with the app)
        User user;
        Object result = userUDIDDAO.read(udid, null);
        List<Object> dbUsers = (List<Object>)result;
        if(!dbUsers.isEmpty() && dbUsers.size() == 1 && dbUsers.get(0) != null) {
            user = (User)dbUsers.get(0);
        } else {
            user = new User(udid);
        }
        if(!user.getFacebookID().isEmpty() && !user.getFacebookID().equals(facebookID))
        	return "<html><body>If you are trying to use someone else's phone with Socio installed, " +
        			"you must first have them deauthorize Socio from Facebook so you can then authorize it.</body></html>";
        else if(user.getFacebookID().equals(facebookID)) {
        	return "<html><body>Thanks for authorizing Socio. Shortly you will begin receiving points for your Facebook updates. " +
			"Please give us a few minutes to update your Socio account.</body></html>";
        } else {	
	        // Assign facebook id to user
	        // make sure 2 users don't have the same facebook id (person with app on multiple phones)
	        result = userFacebookIDDAO.read(facebookID, null);
	        dbUsers = (List<Object>)result;
	        if(!dbUsers.isEmpty() && dbUsers.get(0) != null)
	        	return "<html><body>If you are trying to use another phone with Socio installed, " +
	        			"you must first deauthorize Socio from Facebook (for your first phone)" +
	        			" then re-authorize it (for this phone).</body></html>";
        }
        
        user.setFacebookID(facebookID);
        if(!userFacebookIDDAO.create(user))
        	return "<html><body>Problem accepting authorization. Please try authorizing us again later</body></html>";
		
		return "<html><body>Thanks for authorizing Socio. Shortly you will begin receiving points for your Facebook updates. " +
				"Please give us a few minutes to update your Socio account.</body></html>";
	}
	
	@Path("/deauthorize")
    @POST
    @Produces(MediaType.TEXT_PLAIN)
	public String handleDeauthorization(String signedRequestString) throws Exception {
		
		// preliminary checks before any real work gets done
		String signedRequest = getParamsMap(signedRequestString).get("signed_request");
		if(signedRequest == null)
			throw new IllegalArgumentException("Bad signed request from request: " +signedRequestString);
		String[] sigJSON = signedRequest.split("\\.", 2);
		byte[] signature = new Base64(true).decode(sigJSON[0].getBytes());
		JSONObject jsonObject = new JSONObject(new String(new Base64(true).decode(sigJSON[1])));
		
		// check algorithm
		if(!jsonObject.getString("algorithm").equalsIgnoreCase("HMAC-SHA256"))
			throw new IllegalArgumentException("Bad algorithm from request: " +signedRequestString);
		
		// check signature
		Mac mac = Mac.getInstance("HmacSHA256");
		SecretKeySpec secret = new SecretKeySpec(appSecret.getBytes(),"HmacSHA256");
		mac.init(secret);
		byte[] expectedSignature = mac.doFinal(sigJSON[1].getBytes());
		if(!Arrays.equals(expectedSignature, signature))
			throw new IllegalArgumentException("Bad signature from request: " +signedRequestString);

		// setup behaviors
        DAOBehavior userFacebookIDDAO = new UserFacebookIDBehavior();
		
        // find user with facebook id
		String facebookID = jsonObject.getString("user_id");
        Object result = userFacebookIDDAO.read(facebookID, null);
        List<User> dbUsers = (List<User>)result;
        if(dbUsers.isEmpty() || dbUsers.get(0) == null)
        	throw new IllegalArgumentException("Bad facebook id");
        
        // remove facebook id from user
        Cache cache = null;
        User user = dbUsers.get(0);
        try {
	        cache = CacheManager.getInstance().getCacheFactory().createCache(Collections.emptyMap());
        } catch (CacheException e) {}
        if(cache.containsKey(user.getFacebookID()))
        	cache.remove(user.getFacebookID());
        user.setFacebookID("");
        userFacebookIDDAO.create(user);
        
        return "done";
	}
	
	private HashMap<String,String> getParamsMap(String parameterString) {
		HashMap<String,String> paramsMap = new HashMap<String,String>();
		String params[] = parameterString.split("&");
	    for (String param : params) {
	       String temp[] = param.split("=");
	       if(temp.length >= 2)
	    	   paramsMap.put(temp[0], temp[1]);
	    }
	    
	    return paramsMap;
	}

}
